package org.budo.dubbo.protocol.http.client;

import java.util.HashMap;
import java.util.Map;

import org.budo.dubbo.protocol.http.Consts;
import org.budo.support.lang.util.ReflectUtil;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ReferenceConfig;

import lombok.Getter;
import lombok.Setter;

/**
 * @author lmw
 */
@Getter
@Setter
public class BudoDubboProtocolHttpClient implements ApplicationContextAware {
    private String applicationName = this.getClass().getName().replace('$', '-');

    private String dubboReferenceUrl;

    private Boolean lookupReferenceFromApplicationContext = false;

    private Map<String, Object> referenceCacheMap = new HashMap<String, Object>();

    private String filter = "";

    private String serialization = Consts.SerializationDefault; // http调用默认使用json序列化

    private ApplicationContext applicationContext;

    public BudoDubboProtocolHttpClient() {
    }

    public BudoDubboProtocolHttpClient(String dubboReferenceUrl) {
        this.setDubboReferenceUrl(dubboReferenceUrl);
    }

    public Object getReference(String interfaceType) {
        return this.fetchReference(this.getDubboReferenceUrl(), interfaceType);
    }

    public <T> T getReference(Class<T> interfaceType) {
        return (T) this.fetchReference(this.getDubboReferenceUrl(), interfaceType);
    }

    private Object fetchReference(String dubboReferenceUrl, String interfaceType) {
        String cacheKey = dubboReferenceUrl + "#" + interfaceType;

        Object reference = referenceCacheMap.get(cacheKey);
        if (null != reference) {
            return reference;
        }

        synchronized (BudoDubboProtocolHttpClient.class) {
            reference = referenceCacheMap.get(cacheKey);
            if (null != reference) {
                return reference;
            }

            Object initReference = this.initReference(dubboReferenceUrl, interfaceType);
            referenceCacheMap.put(cacheKey, initReference);
            return initReference;
        }
    }

    public Object fetchReference(String dubboReferenceUrl, Class interfaceType) {
        String cacheKey = dubboReferenceUrl + "#" + interfaceType;

        Object reference = referenceCacheMap.get(cacheKey);
        if (null != reference) {
            return reference;
        }

        synchronized (BudoDubboProtocolHttpClient.class) {
            reference = referenceCacheMap.get(cacheKey);
            if (null != reference) {
                return reference;
            }

            Object initReference = this.initReference(dubboReferenceUrl, interfaceType);
            referenceCacheMap.put(cacheKey, initReference);
            return initReference;
        }
    }

    private Object initReference(String dubboReferenceUrl, String interfaceType) {
        if (this.getLookupReferenceFromApplicationContext()) {
            return applicationContext.getBean(ReflectUtil.classForName(interfaceType)); // 从 spring 中获取
        }

        return this.initDubboReference(dubboReferenceUrl, interfaceType); // dubbo 获取
    }

    private <T> T initReference(String dubboReferenceUrl, Class<T> interfaceType) {
        Boolean lookupFromContext = this.getLookupReferenceFromApplicationContext();
        if (lookupFromContext) {
            return this.applicationContext.getBean(interfaceType); // 从 当前 spring context 中获取
        }

        return this.initDubboReference(dubboReferenceUrl, interfaceType); // dubbo 获取
    }

    private Object initDubboReference(String dubboReferenceUrl, String interfaceType) {
        if (null == dubboReferenceUrl || dubboReferenceUrl.trim().isEmpty() //
                || dubboReferenceUrl.trim().startsWith("${") //
                || null == interfaceType || interfaceType.trim().isEmpty()) { // 参数检查
            throw new IllegalArgumentException("#116 dubboReferenceUrl=" + dubboReferenceUrl + ", interfaceType=" + interfaceType + ", this=" + this);
        }

        ReferenceConfig<Object> referenceConfig = new ReferenceConfig<Object>();
        referenceConfig.setGeneric(true);
        referenceConfig.setInterface(interfaceType);
        referenceConfig.setApplication(new ApplicationConfig(this.getApplicationName()));

        if (!StringUtils.isEmpty(this.getFilter())) {
            referenceConfig.setFilter(this.getFilter());
        }

        referenceConfig.setUrl(dubboReferenceUrl);

        if (null == referenceConfig.getParameters()) {
            referenceConfig.setParameters(new HashMap<String, String>());
        }

        referenceConfig.getParameters().put(Constants.SERIALIZATION_KEY, this.getSerialization());

        Object reference = referenceConfig.get();
        return reference;
    }

    private <T> T initDubboReference(String dubboReferenceUrl, Class<T> interfaceType) {
        Assert.hasText(dubboReferenceUrl, "#61 dubboReferenceUrl can not be empty");

        ReferenceConfig<T> referenceConfig = new ReferenceConfig<T>();
        referenceConfig.setInterface(interfaceType);
        referenceConfig.setApplication(new ApplicationConfig(this.getApplicationName()));

        if (!StringUtils.isEmpty(this.getFilter())) {
            referenceConfig.setFilter(this.getFilter());
        }

        referenceConfig.setUrl(dubboReferenceUrl);

        if (null == referenceConfig.getParameters()) {
            referenceConfig.setParameters(new HashMap());
        }

        referenceConfig.getParameters().put(Constants.SERIALIZATION_KEY, this.getSerialization());

        T reference = referenceConfig.get();
        return reference;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}